package LocalServerTcp;
import generalClasses.ProgramData;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

import LocalServerProtocols.localServerProtocol;


public class TcpClient 
{
	private Socket kkSocket;
	//output stream for sever
	private PrintWriter out;
	//output stream for serialized object
	private ObjectOutputStream oos;
	//input stream for serialized object    
	private ObjectInputStream ois;
	//input stream from user 
	private BufferedReader in;
	private String destinationServerName;
	private int destinationServerPort;
	private String serverResponse;
	private String userResponse;
	private BufferedReader stdIn;
	
	private boolean _connected;
	private int _numberOfConnectionAttempts;
	private final int NUMBER_OF_Attempts = 2;

	//The response the server should send after connection
	//was initiated
	private String serverDesieredResponse;
	

	/**
	 * Create a TCP connection to mainServer<br>
	 * The details of mainServer are taken from ProgramDada
	 * 
	 * @param desieredResponse
	 */
	public TcpClient(String desieredResponse)
	{
		ProgramData pd = new ProgramData();
		destinationServerName = pd.getMainServerAdress();
		destinationServerPort = pd.getMainServerPort();
		kkSocket = null;
        out = null;
        oos = null;
        ois = null;
        in = null;
        serverDesieredResponse = desieredResponse;
        _connected = false;
        _numberOfConnectionAttempts = 0;
        this.initConnectios();
	}
	/**
	 * @param desieredResponse - not in used
	 * @param destinationHostname - Hostname of server you wish to connect 
	 * @param destinationPort - Port that server you wish to connect listen too
	 */
	public TcpClient(String desieredResponse, String destinationHostname, int destinationPort)
	{
		destinationServerName = destinationHostname;
		destinationServerPort = destinationPort;
		kkSocket = null;
        out = null;
        oos = null;
        ois = null;
        in = null;
        serverDesieredResponse = desieredResponse;
        _connected = false;
        _numberOfConnectionAttempts = 0;
        this.initConnectios();
	}
	/**
	 * Initializes all input and output streams a connection has
	 */
	private void initConnectios()
	{
		try 
	        {
				_numberOfConnectionAttempts++;
				System.out.println("\ntrying to connect to: " + destinationServerName +"\n");
	            kkSocket = new Socket(destinationServerName, destinationServerPort);
	            out = new PrintWriter(kkSocket.getOutputStream(), true);
	            oos = new ObjectOutputStream(kkSocket.getOutputStream());
	            ois = new ObjectInputStream(kkSocket.getInputStream());
	            in = new BufferedReader(new InputStreamReader(kkSocket.getInputStream()));
	            stdIn = new BufferedReader(new InputStreamReader(System.in));
	            _connected = true;
	            System.out.println("\nSuccessfully connected to: " + destinationServerName +"\n");
	        } 
	    catch (UnknownHostException e) 
	        {
	            System.err.println("Don't know about host:" +
	            		destinationServerName);
	            System.err.println("\nCouldn't connect to: " + destinationServerName);
//	            System.exit(1);
	        } 
	    catch (IOException e) 
	        {
	            System.err.println("Couldn't get I/O for the connection to: " +
	            		destinationServerName);
	            System.err.println("\nCouldn't connect to: " + destinationServerName);
//	            System.exit(1);
	        }
	    
	    //Some error has occurred and there in no connection to MAIN or BACKUP server
	    if ( _connected == false )
		{
			ProgramData pd = new ProgramData();
	    	destinationServerName =  pd.getBackupServerAdress();
	    	destinationServerPort = pd.getBackupServerPort();
	    	
	    		//we didn't try to connect to backup server yet
	    	if (_numberOfConnectionAttempts < NUMBER_OF_Attempts )
			{
	    		initConnectios();	
			} 
		}
	}
	/**
	 * This method execute the connection's initialization of each user client with any server.<br>
	 * Afterwards it execute the protocol that is given as parameter.
	 * @param protocol - Protocol to be executed
	 * @param action - Action that protocol aims to perform
	 * @return String -  Result of protocol execution
	 */
	// This function in runs the communication between client and server
	// @protocol - the type of protocol to be used in the communication
	public Object run(localServerProtocol protocol, String action)
	{
		String serverResponse = null;
		
		//if destination server is not available
		if (this.in == null)
		{
			//return current date 
			return Integer.valueOf( Integer.MAX_VALUE );
		}
		//destination server is available
		else
		{
			//print current action
			System.out.println("The action the Protocol try to perform is: "+action);
			
			//Call the needed protocol and send him the action to perform
			Object obj = protocol.exec(oos, out, in, ois, action);
			if ( obj instanceof String )
			{
				String protocolResult = (String) obj;
				
				if ( protocolResult.equalsIgnoreCase("Succcess") )
				{
					System.out.println("Authenticaion was successful");
					this.closeConnections();
				}
				else
				{
					System.out.println("Authenticaion FAILED");
					this.closeConnections();
				} 
			}
			else if (obj instanceof Integer) 
			{
				Integer counterValue = (Integer)obj;
				this.closeConnections();
				return counterValue.intValue();
			}
			this.closeConnections();		
			return null;
		}
	}
	
	/**
 	 * Close all streams of a connection and removes it from knokKnokClientMap.
 	 */
	private void closeConnections()
		{
			try 
			{
				out.close();
				in.close();
				stdIn.close();
				kkSocket.close();
			}
			catch (IOException e) 
			{
				e.printStackTrace();
				System.out.println(this.getClass().getSimpleName() + ": Couldn't colose connection with " + destinationServerName);
			}
		}
	
	/**
	 * @return the _connected
	 */
	public boolean isConnected()
	{
		return _connected;
	}
	
}